package MainProgram;

import Algorithms.BackPropagation;
import NeuronNetworkLibrary.Network;
import java.util.Arrays;
import java.util.Random;

/**
 * Main class for Backpropagation algorithm.
 *
 * @author Zbyszko
 */
public class Main {

    public final static int EPOCH = 1000;
    public final static double ERROR = 10e-30;

    public static void main(String[] args) {

        SinusCreator data = new SinusCreator(100);
        //SimpleDataCreator data = new SimpleDataCreator();
        // Create and build the network.
        Network network = new Network(data.getTrainingSet(), data.getDesiredOutput(),
                new int[]{16}, new String[]{"sigmo"}, "linear");

        int i = 0;
        int order[]; 
        do {
            // Randomize which trainingSet will be trained.
           order = randomizeOrder(network.getNumberOfPatterns());
            for (int j = 0; j < network.getNumberOfPatterns(); j++) {
                
                // Calculate forwardly the network outputs.
                network.calculateNetwork(order[j]);

                // Use Backpropagation algorithm to change the weights. 
                BackPropagation.calculateBackPropagation(network);
            }
            i++;
        } while (i <= EPOCH && network.getMSE() > ERROR);

        String units = Arrays.toString(network.getNeuronsInHiddenLayers());
        Plot plot = new Plot(
                "Neural Network", //window title 
                "Backpropagation of sinus; hidden units: " + units, //plot title
                data.getTrainingSet(), //training set 
                data.getDesiredOutput(), //desired output
                printFinalResults(network) //obtained result
                );
        plot.setVisible(true);
                        ;
    }

    /**
     * Randomize the order of the pattern which will be given to train.
     *
     * @param patternsNumber Number of patterns
     * @return An array with the random orders of patterns(training sets) given
     * to neural network for traing.
     */
    public static int[] randomizeOrder(int patternsNumber) {

        Random randomValue = new Random();  // Random number generator
        int[] order = new int[patternsNumber];

        //--- Initialize the array to the ints 0-patternsNumber
        for (int i = 0; i < order.length; i++) {
            order[i] = i;
        }

        //--- Shuffle by exchanging each element randomly
        for (int i = 0; i < order.length; i++) {
            int randomPosition = randomValue.nextInt(order.length);
            int temp = order[i];
            order[i] = order[randomPosition];
            order[randomPosition] = temp;
        }
        return order;
    }

    /**
     * Prints the result of training all sets within neural network.
     *
     * @param network The neural network.
     * @return An array with obtained results.
     */
    public static double[][] printFinalResults(Network network) {

        int patternNumber = network.getNumberOfPatterns();
        int patternLength = network.getOutputLayer().size();

        double[][] obtainedResults = new double[patternNumber][patternLength];
        for (int i = 0; i < network.getNumberOfPatterns(); i++) {
            network.calculateNetwork(i);

            System.out.println("\nPattern nr: " + (i + 1));
            for (int j = 0; j < network.getOutputLayer().size(); j++) {
                System.out.println(network.getOutputLayer().get(j));
                obtainedResults[i][j] = network.getOutputLayer().get(j).getCalculatedOutput();
            }
        }
        System.out.println("\nMSE: " + network.getMSE());

        return obtainedResults;
    }
}
